package ch.quantasy.android.speech;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.util.Log;
import ch.quantasy.android.speech.string.LocalizedString;

public class SpeechService extends Service implements
		OnUtteranceCompletedListener {
	private static final String TAG = "Speech";
	private TextToSpeech tts;
	private Lock babelLock;
	private Condition babelLockCondition;
	private List<LocalizedString> phrases;
	private HashMap<String, String> utteranceOptions;
	private String MESSAGE_COMPLETE;
	private boolean isBabeling;
	private boolean stop;
	private Thread babelThread;

	public static final String SPEECH_ACTON = "ch.quantasy.android.speech.SPEECH_SERVICE";

	public static final Intent getSpeechIntent() {
		return new Intent(SpeechService.SPEECH_ACTON);
	}

	public static Intent getSpeechIntent(
			final LocalizedString... localizedStrings) {
		final Intent speechIntent = SpeechService.getSpeechIntent();
		if ((localizedStrings != null) && (localizedStrings.length > 0)) {
			speechIntent.putExtra("Phrase", localizedStrings);
		}
		return speechIntent;
	}

	public static void setPhrase(final Intent speechIntent,
			final LocalizedString... localizedStrings) {
		speechIntent.putExtra("Phrase", localizedStrings);
	}

	public SpeechService() {
	}

	public SpeechService getService() {
		return SpeechService.this;
	}

	@Override
	public IBinder onBind(final Intent intent) {
		return null;
	}

	@Override
	public int onStartCommand(final Intent intent, final int flags,
			final int startId) {
		if (intent == null) {
			return super.onStartCommand(intent, flags, startId);
		}
		Log.d(SpeechService.TAG, "Speech-Intent received: " + intent);
		final Object[] phrases = (Object[]) intent.getExtras().getSerializable(
				"Phrase");
		if (phrases != null) {
			for (final Object phrase : phrases) {
				if (phrase instanceof LocalizedString) {
					this.addPhrase((LocalizedString) phrase);
				}
			}
		}
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onCreate() {
		super.onCreate();

		this.babelLock = new ReentrantLock();
		this.babelLockCondition = this.babelLock.newCondition();
		this.phrases = new ArrayList<LocalizedString>();
		this.tts = new TextToSpeech(this.getApplicationContext(),
				this.onInitHandler);
	}

	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		this.stop = true;
		this.babelThread.interrupt();
		super.onDestroy();

	}

	private final OnInitListener onInitHandler = new OnInitListener() {
		@Override
		public void onInit(final int arg0) {
			if (TextToSpeech.SUCCESS == arg0) {
				SpeechService.this.initSpeechEngineOptions();
				SpeechService.this.babelThread = new Thread(new BabelRunner());
				SpeechService.this.babelThread.start();
			}
		};
	};

	private void initSpeechEngineOptions() {
		if (TextToSpeech.LANG_MISSING_DATA == this.tts
				.isLanguageAvailable(Locale.UK)) {
			final Intent installIntent = new Intent();
			installIntent
					.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
			installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			this.startActivity(installIntent);
		}
		this.MESSAGE_COMPLETE = "end of message";
		this.utteranceOptions = new HashMap<String, String>();
		// We are interested in 'Message-Complete'
		this.utteranceOptions.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
				this.MESSAGE_COMPLETE);
		// We attach a listener (ourself) to the tts for 'Message-Complete'
		this.tts.setOnUtteranceCompletedListener(this);
		// Give it some speed
		this.tts.setSpeechRate(1.2f);
	}

	/**
	 * @param phrase
	 *            a localizedString or null if you want to end the application
	 */
	public synchronized void addPhrase(final LocalizedString phrase) {
		try {
			this.babelLock.lock();
			this.phrases.add(phrase);
			this.babelLockCondition.signalAll();
		} finally {
			this.babelLock.unlock();
		}
	}

	// Every time called, it will remove a line and loads the tts with the
	// data. (This method returns immediately.
	private void babel(final LocalizedString phrase) {
		Log.i(SpeechService.class.getName(), "speaking: " + phrase);
		if (phrase == null) {
			return;
		}
		try {
			this.babelLock.lock();
			this.isBabeling = true;
			// We tell the tts which language to use for pronounciation
			this.tts.setLanguage(phrase.locale);
			// We tell the tts the sentence which it shall speak
			this.tts.speak(phrase.string, TextToSpeech.QUEUE_FLUSH,
					this.utteranceOptions);
		} finally {
			this.babelLock.unlock();
		}
	}

	class BabelRunner implements Runnable {
		@Override
		public void run() {
			try {
				while (!SpeechService.this.stop) {
					try {
						SpeechService.this.babelLock.lock();
						while (SpeechService.this.isBabeling
								|| SpeechService.this.phrases.isEmpty()) {
							SpeechService.this.babelLockCondition.await();
						}
						SpeechService.this.babel(SpeechService.this.phrases
								.remove(0));
					} finally {
						SpeechService.this.babelLock.unlock();
					}
				}
			} catch (final InterruptedException e) {
			}
		}
	}

	@Override
	public synchronized void onUtteranceCompleted(final String uttId) {
		if (uttId.equals(this.MESSAGE_COMPLETE)) {
			// If the tts has finished speaking, the next sentence shall be
			// spoken...
			try {
				this.babelLock.lock();
				this.isBabeling = false;
				this.babelLockCondition.signalAll();
			} finally {
				this.babelLock.unlock();
			}
		}
	}

}
